import { lib, MD5 } from 'crypto-js';
import { LoadingPlugin, MessagePlugin, Progress, UploadFile } from 'tdesign-vue-next';
import { h, ref } from 'vue';

import { getOriginFileInfo, uploadFileApi } from '../../apps/files/api';

/**
 * 将Blob数据流转换为文件并下载。
 * @param stream BlobPart类型的数据流，代表要下载的数据。
 * @param title 下载文件的标题。
 */
export function toFile(stream: BlobPart, title: string) {
  // 创建一个Blob对象，指定数据类型为Excel应用
  const blob = new Blob([stream], { type: 'application/vnd.ms-excel' });
  // 生成一个指向Blob对象的URL
  const objectUrl = URL.createObjectURL(blob);
  // 创建一个<a>元素用于执行下载
  const a = document.createElement('a');
  // 设置<a>元素的href为Blob对象的URL，指定下载的文件名
  a.setAttribute('href', objectUrl);
  a.setAttribute('download', title);
  // 触发<a>元素的点击事件，开始下载
  a.click();
}

/**
 * 异步下载文件流
 * @param url 文件流的URL地址
 * @param fileName 下载后文件的命名
 * @throws 当响应状态非2xx时抛出错误
 */
export async function downloadFileStream(url: string, fileName: string) {
  // 发起GET请求获取文件流
  const response = await fetch(url, {
    method: 'GET',
    headers: {
      // 添加必要的请求头，指定内容类型为文件流
      'Content-Type': 'application/octet-stream',
    },
  });
  // 检查响应状态是否正常
  if (!response.ok) {
    MessagePlugin.error('文件下载失败');
    return;
  }
  // 将响应数据转换为Blob对象，以便进一步处理
  const blob = await response.blob();

  toFile(blob, fileName); // 将Blob对象转换为文件并进行相关处理，此处省略了具体实现
}

/**
 * 获取文件的扩展名。
 * @param filename 文件名，类型为字符串。
 * @returns 返回文件名中最后一个'.'后边的字符，即文件扩展名。
 */
export function getFileExtension(filename: string) {
  // 从文件名中找到最后一个'.'的位置，并返回其后两位字符作为扩展名
  return filename.slice(Math.floor(filename.lastIndexOf('.') - 1) + 2);
}

/**
 * 根据文件名获取文件类型
 * @param filename 文件名，字符串类型
 * @returns 返回文件类型的字符串，如 'image'、'video'、'audio' 或者 'unknown'（未知类型）
 */
export function getFileType(filename: string) {
  // 将文件名的拓展名转换为小写，并提取出来
  const extension = filename.toLowerCase().split('.').pop();

  // 定义文件类型与对应拓展名的映射关系
  const fileTypes: Record<string, string[]> = {
    image: ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
    video: ['mp4', 'avi', 'mov', 'wmv', 'mkv'],
    audio: ['mp3', 'wav', 'ogg', 'aac'],
    document: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'],
    // 这里可以添加其他文件类型与拓展名的映射
  };

  // 遍历映射关系，匹配文件拓展名，确定文件类型
  for (const type in fileTypes) {
    if (fileTypes[type].includes(extension)) {
      return type;
    }
  }

  // 若无匹配，则返回 'unknown' 代表未知类型
  return 'unknown';
}

interface Options {
  fields?: string[];
}

/**
 * 提取数据中的特定字段。
 *
 * 该函数接受一个数据对象和一个选项对象作为参数，根据选项中指定的字段，从数据中提取出这些字段的值。
 * 如果数据是JSON字符串，函数会尝试将其解析为对象。
 * 如果数据是数组，函数会返回一个新数组，其中每个元素只包含指定字段。
 *
 * @param data 待处理的数据，可以是对象、数组或JSON字符串。
 * @param options 处理数据时的选项，包括要提取的字段列表。
 * @returns 返回提取字段后的新数据，如果处理失败则返回 []。
 */
export function extractFields(data: UploadFile[], options: Options = {}): any[] {
  const { fields } = options;

  // 如果数据是字符串，尝试将其解析为JSON对象
  if (typeof data === 'string') {
    try {
      data = JSON.parse(data);
    } catch (error) {
      console.error('Invalid JSON string:', data);
      return [];
    }
  }

  // 如果数据是数组，并且指定了要提取的字段
  if (Array.isArray(data)) {
    if (fields && fields.length > 0) {
      return data.map((item) => {
        const extracted: Record<string, any> = {};
        fields.forEach((field) => {
          if (field in item) {
            extracted[field] = item[field];
          }
        });
        return extracted;
      });
    }
    return data;
  }

  // 如果数据是null，直接返回null
  if (data === null) {
    return [];
  }

  return [];
}

// 定义文件信息接口
interface FileInfo {
  fileName: string;
  fileSize: number;
  fileMD5: string;
}
/**
 * 异步函数：获取文件信息。
 *
 * 该函数接收一个File对象作为参数，返回一个包含文件名、文件大小和MD5哈希值的Promise对象。
 * 主要用于在前端对上传的文件进行基本信息的处理和校验。
 *
 * @param file File对象，代表待处理的文件。
 * @returns 返回一个Promise，解析为一个包含文件名、文件大小和MD5哈希值的对象。
 */
async function getFileInfo(file: File): Promise<FileInfo> {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();

    fileReader.onload = () => {
      const arrayBuffer = fileReader.result as ArrayBuffer;

      // 将 Array Buffer 转换为 WordArray
      const wordArray = lib.WordArray.create(arrayBuffer);

      // 计算文件 MD5 值
      const fileMD5 = MD5(wordArray).toString();

      // 返回文件信息
      resolve({
        fileName: file.name,
        fileSize: file.size,
        fileMD5,
      });
    };

    fileReader.onerror = () => {
      reject(new Error('文件读取失败'));
    };

    // 读取文件为 Array Buffer
    fileReader.readAsArrayBuffer(file);
  });
}

/**
 * 将文件分片成指定大小的块
 * @param file 要分片的文件
 * @param chunkSize 分片大小（字节）
 * @returns 分片信息数组，每个元素包含分片的 Blob 和索引
 */
function chunkFile(file: File, chunkSize: number): { chunk: Blob; index: number }[] {
  const chunks: { chunk: Blob; index: number }[] = [];
  const totalChunks = Math.ceil(file.size / chunkSize);

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);
    chunks.push({ chunk, index: i });
  }

  return chunks;
}

/**
 * 上传文件
 * @param file 待上传的文件
 * @param size 每个分片的大小（以KB为单位），默认为1024KB
 * @param isInterdict 是否禁止上传时刷新页面或退出，用于重要文件的上传，默认为false
 * @param waitForCompletion 是否等待上传完成再返回，用于处理上传过程中的UI反馈等，默认为true
 * @returns 返回一个Promise，包含上传完成后的文件ID和URL如果waitForCompletion为false，则直接返回文件ID和URL
 */
export async function uploadFile(file: File, size = 1024 * 5, isInterdict = false, waitForCompletion = true) {
  // 获取文件的MD5值和文件名
  const { fileMD5, fileName } = await getFileInfo(file);

  // 计算分片大小，并生成分片数组
  const chunkSize = size * 1024;
  const chunks = chunkFile(file, chunkSize);

  // 获取原始文件信息，用于判断文件是否已经存在，并获取文件的相关信息
  const {
    id: originFileId,
    has,
    url,
  } = await getOriginFileInfo({
    fileName,
    areaNum: chunks.length,
    md5: fileMD5,
  });

  // 初始化上传实例和进度
  let instance: any = null;
  const schedule = ref<number>(0);

  // 创建一个Promise，用于等待上传完成
  let uploadComplete: (value: { id: string; url: string }) => void;
  let uploadError: (error: Error) => void;
  const uploadCompletePromise = new Promise<{ id: string; url: string }>((resolve, reject) => {
    uploadComplete = resolve;
    uploadError = reject; // 添加reject回调
  });

  // 错误处理函数
  const handleError = (error: Error) => {
    if (isInterdict) {
      instance.hide();
    } else {
      MessagePlugin.close(instance);
    }
    MessagePlugin.error(`上传失败: ${error.message}`);
    uploadError(error);
  };

  // 上传分片的函数
  async function uploadChunk(index: number) {
    // 第一个分片上传时，根据isInterdict值决定是否显示不同的加载提示
    if (index === 0) {
      if (isInterdict) {
        instance = LoadingPlugin({
          fullscreen: true,
          attach: 'body',
          preventScrollThrough: false,
          indicator: () =>
            h('div', {}, [
              h(
                'p',
                {
                  style: {
                    lineHeight: 2,
                  },
                },
                '⚠️⚠️ 文件上传中，请不要刷新网页或退出当前页面！',
              ),
              h(Progress, {
                theme: 'plump',
                percentage: schedule.value,
                style: {
                  width: '100%',
                },
              }),
            ]),
        });
      } else {
        instance = MessagePlugin.warning({
          content: () =>
            h('div', {}, [
              h('p', {}, '上传中，请不要刷新网页或退出当前页面！'),
              h(Progress, {
                percentage: schedule.value,
                style: {
                  width: '300px',
                },
              }),
            ]),
          duration: 0,
        });
      }
    }
    // 更新上传进度
    schedule.value = Math.floor((index / chunks.length) * 100);

    // 所有分片上传完成后，隐藏加载提示，并显示上传成功消息
    if (index >= chunks.length) {
      if (isInterdict) {
        instance.hide();
      } else {
        MessagePlugin.close(instance);
      }
      MessagePlugin.success('上传成功');

      // 上传完成，resolve Promise
      uploadComplete({ id: originFileId, url });
      return;
    }

    // 获取当前分片并读取为ArrayBuffer
    const { chunk } = chunks[index];
    const reader = new FileReader();
    reader.readAsArrayBuffer(chunk);
    reader.onload = async (e) => {
      const arrayBuffer = e.target?.result as ArrayBuffer;
      const wordArray = lib.WordArray.create(arrayBuffer);
      const hash = MD5(wordArray).toString();

      // 创建表单数据，将分片和相关信息添加进去
      const formData: any = new FormData();
      formData.append('file', new Blob([arrayBuffer]), file.name);
      formData.append('area', (index + 1).toString());
      formData.append('totalChunks', chunks.length.toString());
      formData.append('md5', hash); // 添加MD5哈希值
      formData.append('id', originFileId); // 添加原始文件ID

      try {
        // 上传分片到服务器
        const response = await uploadFileApi(formData);

        // 检查响应状态
        if (response.status !== '200') {
          throw new Error(`上传失败，状态码: ${response.status}, 错误信息: ${response.message}`);
        }

        // 继续上传下一个分片
        await uploadChunk(index + 1);
      } catch (error) {
        handleError(new Error(`上传失败: ${error.message}`));
      }
    };
  }

  // 如果文件不存在，则开始上传
  if (!has) {
    uploadChunk(0);
  }

  // 根据参数决定是否等待上传完成
  if (!has && waitForCompletion) {
    return uploadCompletePromise;
  }

  // 如果文件已存在或不需要等待上传完成，则直接返回文件ID和URL
  return {
    id: originFileId,
    url,
  };
}
